线程池
线程池的优势
- 1:降低资源的消耗,通过重复利用已经创建的线程,降低线程的创建和销毁造成的资源的消耗
- 2:提高响应速度,当任务到达时,任务可以不需要去等待线程的创建就可以执行
- 3:提高线程的可管理,线程是稀缺资源,如果无限制的创建,会消耗系统的资源,降低系统的稳定性,但是使用线程池,可以进行统一的分配调优, 监控.
- 4:提供了定时执行,定期执行,单行程,并发数控制等功能…
new Thread的弊端
- 每次new Thread新建对象,性能差
- 线程缺乏统一管理,可能无限制的新建线程,相互竞争,有可能过多的占用系统的资源
- 缺少更多的功能,如更多执行,定期执行,线程中断
JDK提供了一套Executer框架,可以帮助开发人员,有效的进行线程控制,其中newFixedThreadPool(),newSingleThreadExecutor(),newCachedThreadPool均使用了 ThreadPoolExecutor,下面部分就是了解ThreadPoolExecutor
1. ThreadPoolExecutor的简单使用
创建线程池并使用
1 | public class newMyThreadPool { |
1.1构造方法参数:
参数名 | 解释 |
---|---|
corePoolSize | 线程池的基本大小,运行时,当任务提交过来了,如果线程池中的线程数小于corePoolSize,即便当前的线程数够用了,也会继续创建新线程,当作辅助线程 |
maximumPoolSize | 线程中最大的线程数量 |
keepAliveTime | 当线程池中线程的数量达到corePoolSize的时候,此参数为到终止前,超出了corePoolSize的多余线程等待新任务的最长时间,时间到了还没有任务,就被销毁 |
unit | keepAliveTime的时间单位 |
workQueue | 任务队列,保存被提交的尚未执行的任务,(仅仅保存由execute方法提交的 Runable 任务) |
threadFactory | 执行程序创建新线程时使用的工厂。一般使用默认的即可 |
handler | 拒绝策略,当任务太多,来不及处理时,如何拒绝的策略 |
1.2 详解workQueue和handler
workQueue
- ArrayBlockingQueue:一个基于数组结构的有界数组队列,按照FIFO的原则,对任务进行排序
- LinkedBlockingQueue:基于链表结构的阻塞队列,同样按照FIFO排序元素,吞吐量通常高于ArrayBlockingQueue
- Excutor的静态工厂方法,Executors.newFixedThreadPool()使用了这个队列
- SynchronizedQueue: 一个不存储元素的阻塞队列,每个插入操作,必须等待另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常高于LinkedBlockingQueue高—它将任务直接提交给线程而不保持它们
- 静态工厂方法 Executors.newCachedThreadPool使用了这个队列
- PriorityBlockingQueue: 一个具有优先级的无限阻塞队列
handler
RejectedExecutionHandler饱和策略,当线程池和队列都满了,这个时候,又来了新的任务,那么久必须采用一种策略处理新的任务,这个策略默认使用的是abortPolicy,(流产策略,表示无法处理新任务而抛出异常)
- DiscardRunsPolicy:丢弃线程中最近的一个任务,并执行当前任务
- CallerRunsPolicy: 只用调用者所在的线程来执行任务
- DiscardPolicy: 不处理,丢弃掉
- 实现RejectedExecutionHandler接口自定义策略,如记录日志,或者持久化不能处理的任务
1.3 线程任务的提交
execute() & submit()
- execute()方法用于提交不需要返回值的任务,意味着,无法得知任务是否正常执行
1 | // 提交不带返回值的线程任务 |
- submit()用于提交一个任务,并带有返回值,submit()将返回一个Future类型的对象,然后我们可以通过这个对象判断任务是否执行成功并且可以同futrue.get()的方法获取返回值并使用,get()会阻塞当前线程,直到任务完成
1 | //提交带返回值的线程任务 |
1.4 shutdown()与shutdownNow()方法
区别
- 执行shutdown()方法后,会等待已经添加进来的任务全部执行完后,在关闭线程池
- 将线程的状态设置成SHUTDOWN,然后去中断没有执行任务的线程
- shutdownNow(),不管任务是否在执行,中断任务,关闭线程池
- 将线程的状态修改为STOP,然后尝试停止所有正在执行的线程任务,并返回等待执行任务的列表
相同点
都是遍历线程池中的工作线程,挨个调用它们的interrupt()方法
2 合理配置线程池
根据任务的特性,合理配置线程池
- 任务性质:
- PUC密集型任务(绝大部分时间花费在计算上)
- 应该配置尽可能少的线程,如N+1 N是CPU数 Runtime.getRuntime().availableProcessors()
- IO密集型任务(大部分时间花费在等待IO上)
- 应配置尽可能多的线程,如2*N
- 混合型任务
- 拆分成一个任务密集型和一个io密集型
- PUC密集型任务(绝大部分时间花费在计算上)
- 任务的优先级: 高,底,中
- 任务执行时间: 长,中,短
- 任务的依赖性: 是否依赖其他系统资源,如数据库连接
3ThreadPoolExecutor同样提供了很多线程池监控的方法
方法名 | 描述 |
---|---|
beforeExecute(Thread t, Runnable r) | 在Runable执行任务之前调用 |
afterExecute(Runnable r, Throwable t) | Runnable任务执行完后调用 |
getActiveCount() | 主动执行任务的近似线程数 |
等等..
Executor多线程框架
如果ThreadPoolExecutor是一个线程池,那么Executors就是一个线程池工厂
java.util.concurrent
类 Executors
java.lang.Object
继承者 java.util.concurrent.Executors
Executor是一个很灵活的基于接口的任务执行工具,使用它可以极为简单的创建出一个很棒的任务工作队列,却只需要一行代码
1 | ExecutorService executor = Executors.newCachedThreadPool(); |
提交一个Runable方法
1 | executor.execute(Runable); |
优雅的终止
1 | executor.shutdown(); |
查看他的实现,可以看到,其实他就是使用ThreadPoolService实现的
1 | public static ExecutorService newFixedThreadPool(int nThreads) { |
2 返回一个根据实际情况进行调整,没有固定大小的线程池,如果有空闲的线程,优先调用空闲的线程,没有空闲的线程,创建新的线程执行任务,他在执行的过程中通常会创建和所需线程数量相等的线程数,因此它是合理的Executor首选
1
public static ExecutorService newCachedThreadPool();
- 3 返回一个只有一个线程的线程池,任务被提交后,如果线程空闲,那么由此线程执行任务,多余的任务被保存进任务队列,按照先进先出的顺序,排队等待执行
为什么单个线程还整一个线程池? 因为 new 出来的线程在执行任务时如果挂掉了,那么任务就不会被继续执行下去,而使用线程池,虽然只有一个线程,但是线程挂掉后,依然会创建出新的线程执行任务…(用于更新本地或远程日志,或者时间分发线程)
1 | public static ExecutorService newSingleThreadExecutor(); |
- 4 返回一个ScheduledExecutorService对象,线程池的大小是corePoolSize, ScheduledExecutorService 继承了ExecutorService接口,并且对他进行了升级,添加了 在给定的延迟后执行某些任务的,或者周期性执行某些任务的方法
1 | public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) { |
简单使用1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34public static void main(String[] args) {
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);
/*
while (true) {
scheduledExecutorService.schedule(new Runnable() {
@Override
public void run() {
System.out.println("-->" + Thread.currentThread().getName());
}
}, 5, TimeUnit.SECONDS);
}
*/
ScheduledFuture<String> schedule = scheduledExecutorService.schedule(new Callable<String>() {
public String call() throws Exception {
System.out.println(Thread.currentThread().getName());
return Thread.currentThread().getName();
}
}, 3, TimeUnit.SECONDS);
try {
System.out.println(schedule.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
- 5 返回一个只有一个线程的线程池
1 | public static ScheduledExecutorService newSingleThreadScheduledExecutor() { |
JDK8新添加:
1 | public static ExecutorService newWorkStrealingPool |
使用:
1 | public static void main(String[] args) { |
- 任务窃取线程池,什么是任务窃取?就是让闲置的线程去执行本不属于他们的任务, 带并行级别,并行级别就是同一时刻,最多有多少条线程同时执行,达到减少竞争的效果,,和CPU数相关,如果不设置参数,默认就是CPU的个数.
参考书籍<<java编程思想>>Bruce Eckel著 <